查看原文
其他

中国移动ML302模组(4G Cat.1 通信模组)TencentOS-tiny AT模组框架适配

杨源鑫 嵌入式应用研究院 2023-06-07

关于AT模组框架,之前学习TencentOS-tiny是有写过一篇文章的,链接如下:

还在用传统的方式驱动一个通信模组?不如一起来学习下TOS的AT模组框架吧!

最近查看了TencentOS-tiny官方仓库,发现device目录下并没有ML302这个模组的驱动,于是我就利用这个机会学习了模组驱动编写与AT模组框架适配,期间遇到了一些技术疑惑请教了世伟兄(mculover666),最后顺利的将代码提交到了TencentOS-tiny官方仓库;能够得以顺利的完成,得感谢世伟兄提供技术支持。

在此之前,我已经给TencentOS-tinyTencentOS-Tiny-IoT-Projects贡献了一些项目,如下所示,有兴趣可持续关注并提ISSUE。

ML302 4G Cat.1模组资料仓库地址:

https://gitee.com/morixinguan/open-source-of-cloud/tree/master/04.4G_Cat.1

ML302 TencentOS-tiny驱动仓库地址:

https://github.com/Tencent/TencentOS-tiny/tree/master/devices/ml302

ML302 4G Cat.1小熊派测试例程仓库地址:

https://github.com/Tencent/TencentOS-tiny/tree/master/board/BearPi_STM32L431RC/KEIL/tcp_through_ml302_4g_cat_1_module
https://github.com/Tencent/TencentOS-tiny/tree/master/examples/tcp_through_ml302_4g_cat_1_module

TencentOS-tiny官方仓库地址:

https://gitee.com/Tencent/TencentOS-tiny
https://github.com/Tencent/TencentOS-tiny

TencentOS-Tiny-IoT-Projects官方仓库地址:

https://gitee.com/Tencent/TencentOS-Tiny-IoT-Projects

在使用AT框架适配模组前,需要先用串口终端测试一下,于是我们找到了模组 的通信流程示例手册:

接下来根据手册要求发送以下指令来进行上电初始化,如果初始化流程没有问题的话,接下来就可以和远程服务器进行通信了,这里的测试平台我用的是中国移动OneNet。

1、移动4G Cat.1 ML302上电初始化流程

1.1、测试模块初始化正常
AT

OK
1.2、查询SIM卡状态
AT+CPIN?

+CPIN: READY

OK
1.3、查询协议栈是否打开
AT+CFUN?

+CFUN: 1

OK
1.4、查询当前信号质量
AT+CSQ

+CSQ: 31,99

OK
1.5、设置 GPRS的APN
AT+CGDCONT=1,"IP","CMIOT"

OK
1.6、激活PDP
AT+CGACT=1,1

+CGACT: 1,1

OK

2、建立TCP连接

测试连接移动OneNet云平台==>透传模式

2.1、进入透传模式
AT+MIPMODE=0,1

OK
2.2、连接服务器
AT+MIPOPEN=0,"TCP","183.230.40.33",80

OK
CONNECT OK
2.3、发送HTTP透传数据
POST /devices/604618155/datapoints?type=3 HTTP/1.1
api-key:NGSRfMY43gLebqieuaX4UfPgoac=     
Host:api.heclouds.com        
Content-Length:11

{"ppm":100}


以下为接收到服务器的信息回复
HTTP/1.1 200 OK
Date: Mon, 22 Mar 2021 09:00:30 GMT
Content-Type: application/json
Content-Length: 26
Connection: keep-alive
Server: Apache-Coyote/1.1
Pragma: no-cache

{"errno":0,"error":"succ"}
2.4、退出数据模式,此时连接依然保持
+++
2.5、关闭连接,释放资源
AT+MIPCLOSE=0

0,CLOSE OK
2.6、退出透传模式
AT+MIPMODE=0,0

OK

我的串口调试助手显示如下:

为了调试方便,这里我用是透传数据的方式来上传数据到OneNet。

3、TencentOS-tiny AT模组框架适配

AT模组驱动位于TencentOS-tiny根目录下的device目录:

在这个目录下,已经适配了很多市面上常用的AT模组,如下所示,在这个目录下创建一个ML302的文件夹,向里面添加ML302模组驱动文件:

接下来根据其它模组的驱动套路添加对应的代码,完成ML302适配SAL层,这里我们根据官方文档描述,找到SAL相关适配的结构体:

//枚举===>协议类型,TCP、UDP
typedef enum sal_protocol_en {
    TOS_SAL_PROTO_TCP,
    TOS_SAL_PROTO_UDP,
} sal_proto_t;
//需要对接SAL的接口
typedef struct sal_module_st {
    int (*init)(void);

    int (*get_local_mac)(char *mac);

    int (*get_local_ip)(char *ip, char *gw, char *mask);

    int (*parse_domain)(const char *host_name, char *host_ip, size_t host_ip_len);

    int (*connect)(const char *ip, const char *port, sal_proto_t proto);

    int (*send)(int sock, const void *buf, size_t len);

    int (*recv_timeout)(int sock, void *buf, size_t len, uint32_t timeout);

    int (*recv)(int sock, void *buf, size_t len);

    int (*sendto)(int sock, char *ip, char *port, const void *buf, size_t len);

    int (*recvfrom)(int sock, void *buf, size_t len);

    int (*recvfrom_timeout)(int sock, void *buf, size_t len, uint32_t timeout);

    int (*close)(int sock);
} sal_module_t;

其中,send、recv、recv_timeout是TCP协议使用的收发接口,而sendto、recvfrom、recvfrom_timeout是UDP协议使用的收发接口,按照要求,实现模组对接SAL层常用的一些接口,然后将这个结构体注册到SAL上。

以init接口为例,我们要实现模组上电初始化要做的一些事情,具体指令请参考对应的手册:

static int ml302_init(void)
{
    printf("Init ML302 ...\n" );
    //测试模组是否上电
    if (ml302_wait_ok() != 0)
    {
        printf("ml302 start FAILED\n");
        return -1;
    }
    //关闭回显
    if (ml302_echo_close() != 0)
    {
        printf("ml302 echo close FAILED\n");
        return -1;
    }
    //测试SIM卡是否已经插上
    if (ml302_pin_ready() != 0)
    {
        printf("ml302 pin not ready\n");
        return -1;
    }
    //测试是否注网成功
    if(ml302_gsm_network_check() != 0)
    {
        printf("ml302 GSM network register status check fail\n");
        return -1 ;
    }
    //测试模组信号强度
    if (ml302_signal_quality_check() != 0)
    {
        printf("ml302 signal quality check status failed\n");
        return -1;
    }
    //设置APN
    if (ml302_set_gprs_apn() != 0)
    {
        printf("ml302 set gprs_apn failed\n");
        return -1;
    }
    //激活PDP
    if (ml302_activate())
    {
        printf("ml302 activate FAILED\n");
        return -1;
    }

    printf("Init ML302 Done\n" );
    return 0;
}

这么多指令里面,其实包含了三类AT指令:

  • 1、只需要判断是否返回OK的AT指令

ml302_wait_ok函数为例,这是第一类AT指令的实现,如下:

static int ml302_wait_ok(void)
{

    int try = 0;

    at_echo_t echo;
    /* 创建一个echo 对象,缓冲区为NULL,期望字符串为NULL */
    tos_at_echo_create(&echo, NULL, 0, NULL);
     /* 尝试检测10次,一旦有一次正常,返回 */
    while (try++ < 10)
    {
        /* 执行AT命令,超时时长1000ms */
        tos_at_cmd_exec(&echo, 1000, "AT\r\n");
        /* 判断执行结果是否为OK */
        if (echo.status == AT_ECHO_STATUS_OK)
        {
           return 0;
        }
    }
    return -1;
}
  • 2、需要判断是否OK,也需要解析执行结果的AT指令

ml302_signal_quality_check函数为例,这是第一类AT指令的实现,如下:

static int ml302_signal_quality_check(void)
{
    int rssi, ber;
    at_echo_t echo;
    char echo_buffer[32], *str;

    int try = 0;
     /* 尝试检测10次,一旦有一次正常,返回 */
    while (try++ < 10)
    {
        /* 创建echo对象,传入一个缓冲区存放AT命令执行结果 */
        tos_at_echo_create(&echo, echo_buffer, sizeof(echo_buffer), NULL);
        /* 执行AT命令,超时时长1000ms */
        tos_at_cmd_exec(&echo, 1000, "AT+CSQ\r\n");
        /* 判断执行结果是否返回了OK */
        if (echo.status != AT_ECHO_STATUS_OK)
        {
            return -1;
        }
        /* 从AT指令的执行结果中解析提取CSQ值进行判断 */
        str = strstr(echo.buffer, "+CSQ:");

        if (!str)
        {
            return -1;
        }

        sscanf(str, "+CSQ:%d,%d", &rssi, &ber);

        if (rssi != 99)
        {
            return 0;
        }
    }

    return -1;
}
  • 3、模组主动上报的数据处理

这一类AT指令对应TCP/IP协议栈接收数据的上报机制,使用AT框架的事件机制进行处理,也就是我们前面提到的这个事件:

at_event_t ml302_at_event[] =
{
    { "+MIPURC: \"recv\",", ml302_incoming_data_process },
};

这个对应下发处理的事件回调函数需要我们自己来实现,如ml302_incoming_data_process,实现如下:

__STATIC__ void ml302_incoming_data_process(void)
{
    uint8_t data;
    int channel_id = 0, data_len = 0, read_len;
    uint8_t buffer[128];

    /*
      //模组上报的数据格式
     +MIPURC: "recv",<sockid>,<datalen>
     <data content>
    */

    /* 读取解析socket id */
    while (1)
    {
        if (tos_at_uart_read(&data, 1) != 1)
        {
            return;
        }

        if (data == ',')
        {
            break;
        }

        channel_id = channel_id * 10 + (data - '0');
    }
    /* 读取解析数据长度 */
    while (1)
    {
        if (tos_at_uart_read(&data, 1) != 1)
        {
            return;
        }

        if (data == '\r')
        {
            break;
        }

        data_len = data_len * 10 + (data - '0');
    }

    if (tos_at_uart_read(&data, 1) != 1)
    {
        return;
    }
    /* 根据解析出的数据长度和缓冲区的长度,循环读取数据内容,写入到对应 socket id 的channel中 */
    do
    {
#define MIN(a, b)   ((a) < (b) ? (a) : (b))
        read_len = MIN(data_len, sizeof(buffer));

        if (tos_at_uart_read(buffer, read_len) != read_len)
        {
            return;
        }

        if (tos_at_channel_write(channel_id, buffer, read_len) <= 0)
        {
            return;
        }

        data_len -= read_len;
    }
    while (data_len > 0);

    return;
}

模组适配完毕后,编写对接后台例程,由于篇幅限制,详细代码请访问TencentOS-tiny官方仓库获取。

测试例程效果如下:

OneNet平台展示效果:

往期精彩

【深度】从微观理解 volatile 行为

读书感悟分享(1)--可复制的领导力

小熊派刷儿子骑摩的的抖音视频,流畅度666666

Linux 驱动开发 / gpio子系统 / 快速入门

手把手教你在STM32上实现OLED视频播放(很简单也很硬很肝!)

觉得本次分享的文章对您有帮助,随手点[在看]并转发分享,也是对我的支持。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存